<?php
namespace App\Http\Service;

use App\Common\ErrorCode\ErrorCode;
use App\Exceptions\BusinessException;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token;

/**
 * 这里整个使用的jwt加密方式为RSA方式，生成的token也需要同样的方式解密
 * Class JwtService
 * @package App\Http\Service
 */
class JwtService
{
    const ISSUER = 'mc.com';    //令牌发布者
    const AUDIENCE = 'mc.com';  //令牌的听众
    const EXP_TIME = 10800;//令牌有效时间
    const NBF_TIME = 7200;//令牌可使用有效时间

    /**
     * 解析字符串
     * @param string $token
     * @return \Lcobucci\JWT\Token
     * @throws BusinessException
     */
    public static function decode(string $token) : Token
    {
        try{
            $parser = new Parser();
            return $parser->parse($token);
        }catch (\Exception $exception){
            throw new BusinessException(ErrorCode::ERROR_JWT_UNABLE_DECODE);
        }
    }

    /**
     * 校验并返回token对象
     * @param string $token
     * @param bool $isVerifyNbf
     * @return Token|string
     * @throws BusinessException
     *
     */
    public static function verify(string $token, $isVerifyNbf = false)
    {
        $signer = new Sha256();
        $publicKey = new Key('file://' . env('PUBLIC_KEY_PATCH'));
        $token = self::decode($token);
        if (!$token->verify($signer, $publicKey)) {
            throw new BusinessException(ErrorCode::ERROR_JWT_UNABLE_DECODE);
        }

        //检查是否在过期令牌列表中

        if ($isVerifyNbf && !self::verifyTime($token)) {
            throw new BusinessException(ErrorCode::ERROR_JWT_TOKEN_OUT_ACTION);
        }
        return $token;
    }

    /**
     * 生成编码加密后的字串
     * @param array $claimData
     * @return string
     */
    public static function encode(array $claimData = [])
    {
        $signer = new Sha256();
        $privateKey = new Key("file://" . env('PRIVATE_KEY_PATCH'));

        $time = time();
        $builder = (new Builder())
            ->issuedBy(self::ISSUER)    //设置令牌发布者
            ->permittedFor(self::AUDIENCE)  //设置令牌接收者
            ->identifiedBy(self::getJwtId())   //设置令牌唯一标示符
            ->issuedAt($time)  //设置令牌发布时间(iat)
            ->canOnlyBeUsedAfter($time + self::NBF_TIME )   //设置令牌使用有效期(nbf）
            ->expiresAt($time + self::EXP_TIME);   //设置令牌有效期(exp)
        foreach ($claimData as $k => $v) {
            $builder->withClaim($k, $v);
        }
        return (string)$builder->getToken($signer, $privateKey);
    }

    //获取令牌唯一标示id
    private static function getJwtId()
    {
        return uniqid('l_', true);
    }

    //校验令牌有效期
    private static function verifyTime(Token $token)
    {
        $nbf = $token->getClaim('nbf');//令牌使用有效期
        if ($nbf >= time()) {
            return true;
        }
        return false;
    }

    //在令牌有效期内换取新的身份令牌
//    public static function getNewToken(Token $token)
//    {
//        $expTime = $token->getClaim('exp');//使用有效期
//        if ($expTime < time()) {
//            throw new BusinessException(ErrorCode::ERROR_JWT_TOKEN_INVALID, '令牌已经失效，无法换取新的令牌');
//        }
//    }
}